using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;

namespace NobleTech.Products.CreatureKingdom
{
    /// <summary>
    /// Returns classes that implement IOrganism from a specified assembly
    /// Checks that the assembly obeys all the rules
    /// All the reflection stuff is contained in this class
    /// </summary>
    public class OrganismLoader
    {
        public static LoadResults FromPath(string path)
        {
            try
            {
                return FromAssembly(Assembly.LoadFile(path));
            }
            catch (FileNotFoundException)
            {
                return new LoadResults(path, Exception.LoadError.FileNotFound);
            }
            catch (FileLoadException)
            {
                return new LoadResults(path, Exception.LoadError.Unloadable);
            }
            catch (BadImageFormatException)
            {
                return new LoadResults(path, Exception.LoadError.BadImage);
            }
        }

        public static LoadResults FromAssembly(Assembly ass)
        {
            LoadResults results = new LoadResults(ass.GetName());
            // Check for dependancies on other assemblies
            //foreach (AssemblyName reference in ass.GetReferencedAssemblies())
            //    results.AssemblyErrors.AddError(Exception.LoadError.ReferencesAssemblies);

            foreach (Type type in ass.GetTypes())
            {
                // Check the IL stream for banned instructions
                //foreach (MethodInfo method in type.GetMethods(BindingFlags.DeclaredOnly))
                //    method.GetMethodBody().GetILAsByteArray();
                // Check for static fields that can be used for communication
                if (type.GetFields(BindingFlags.Static).Length != 0)
                    results.AssemblyErrors.AddError(Exception.LoadError.StaticFields);
                // Check for static constructors that can call code outside of the time slicer
                if (type.GetConstructors(BindingFlags.Static).Length != 0)
                    results.AssemblyErrors.AddError(Exception.LoadError.StaticConstructor);
            }
            // Get all the types that implement IOrganism
            foreach (Type type in ass.GetExportedTypes())
            {
                // Not sure this is necessary:
                if (type.IsAbstract || type.IsGenericType)
                    continue;
                // Look for OrganismAttribute
                if (GetOrganismAttribute(type) == null)
                    continue;
                // We have an organism
                // Check for errors that need to be reported against the type
                LoadResults.OrganismType resType = new LoadResults.OrganismType(type);
                // Check implements interface
                if (!typeof(IOrganism).IsAssignableFrom(type))
                    resType.OrganismErrors.AddError(Exception.LoadError.InterfaceNotImplemented);
                // Check has required constructor
                if (GetConstructorTakingStats(type) == null)
                    resType.OrganismErrors.AddError(Exception.LoadError.NoConstructor);
                // Check has a method
                if (GetStatsGenerator(type) == null)
                    resType.OrganismErrors.AddError(Exception.LoadError.NoStatsGenerator);
                // Check has a renderer
                Type rendererType = GetRendererType(type);
                if (rendererType == null)
                    resType.OrganismErrors.AddError(Exception.LoadError.BadRendererType);
                else if (GetConstructorTakingStats(rendererType) == null)
                    resType.OrganismErrors.AddError(Exception.LoadError.NoRendererConstructor);
                results.Organisms.Add(resType);
            }
            if (results.Organisms.Count == 0)
                results.AssemblyErrors.AddError(Exception.LoadError.NoCreatures);
            return results;
        }

        private static OrganismAttribute GetOrganismAttribute(Type implementationType)
        {
            object[] orgAtts = implementationType.GetCustomAttributes(
                typeof(OrganismAttribute), false);
            if (orgAtts.Length == 0)
                return null;
            return (OrganismAttribute)orgAtts[0];
        }

        private static OrganismBrushAttribute GetOrganismAttribute2(Type implementationType)
        {
            object[] orgAtts = implementationType.GetCustomAttributes(
                typeof(OrganismBrushAttribute), false);
            if (orgAtts.Length == 0)
                return null;
            return (OrganismBrushAttribute)orgAtts[0];
        }

        private static ConstructorInfo GetConstructorTakingStats(Type type)
        {
            return type.GetConstructor(new Type[] { typeof(OrganismStats) });
        }

        private static ConstructorInfo GetConstructorTakingNothing(Type type)
        {
            return type.GetConstructor(new Type[] {});
        }


        private const string GetOriginalStatsMethodName = "GetOriginalStats";

        private static MethodInfo GetStatsGenerator(Type implementationType)
        {
            MethodInfo statsGenerator = implementationType.GetMethod(GetOriginalStatsMethodName,
                BindingFlags.Static | BindingFlags.Public, null, 
                new Type[] { typeof(OrganismStats) }, null);
            if (statsGenerator == null)
                return null;
            if (statsGenerator.ReturnType != typeof(void))
                return null;
            return statsGenerator;
        }

        private static Type GetRendererType(Type implementationType)
        {
            Type rendererType = GetOrganismAttribute(implementationType).Renderer;
            if (rendererType == null)
                return null;
            if (!typeof(IRenderer).IsAssignableFrom(rendererType))
                return null;
            return rendererType;
        }

     

        public static IOrganism CreateImplementation(Type implementationType, OrganismStats stats)
        {
            ConstructorInfo constructor = GetConstructorTakingStats(implementationType);
            if (constructor == null)
                throw new Exception(Exception.LoadError.NoConstructor);
            return (IOrganism)constructor.Invoke(new object[] { stats });
        }

        public static OrganismStats GetOriginalStats(Type implementationType)
        {
            MethodInfo statsGenerator = GetStatsGenerator(implementationType);
            if (statsGenerator == null)
                throw new Exception(Exception.LoadError.NoStatsGenerator);
            OrganismStats stats = new OrganismStats();
            statsGenerator.Invoke(null, new object[] { stats });
            return stats;
        }

        public static IRenderer CreateRenderer(Type implementationType, OrganismStats stats)
        {
            Type rendererType = GetRendererType(implementationType);
            if(rendererType == null)
                throw new Exception(Exception.LoadError.BadRendererType);
            ConstructorInfo constructor = GetConstructorTakingStats(rendererType);
            if (constructor == null)
                throw new Exception(Exception.LoadError.NoRendererConstructor);
            return (IRenderer)constructor.Invoke(new object[] { stats });
        }

        public class LoadResults
        {
            private string assemblyName;
            public string AssemblyName { get { return assemblyName; } }

            private Version assemblyVersion;
            public Version AssemblyVersion { get { return assemblyVersion; } }

            private Exception assemblyErrors;
            public Exception AssemblyErrors { get { return assemblyErrors; } }

            public class OrganismType
            {
                private Type implementationType;
                public Type ImplementationType { get { return implementationType; } }

                private Exception organismErrors = new Exception();
                public Exception OrganismErrors { get { return organismErrors; } }

                public OrganismType(Type implementationType)
                {
                    this.implementationType = implementationType;
                }
            }

            private List<OrganismType> organisms = new List<OrganismType>();
            public IList<OrganismType> Organisms { get { return organisms; } }

            public LoadResults(AssemblyName assemblyName)
            {
                this.assemblyName = assemblyName.Name;
                this.assemblyVersion = assemblyName.Version;
                this.assemblyErrors = new Exception();
            }

            public LoadResults(string path, Exception.LoadError error)
            {
                this.assemblyName = path.Substring(path.LastIndexOf('\\') + 1);
                this.assemblyVersion = null;
                this.assemblyErrors = new Exception(error);
            }
        }

        public class Exception : System.Exception
        {
            public enum LoadError
            {
                StaticFields,
                NoCreatures,
                NoConstructor,
                NoStatsGenerator,
                StaticConstructor,
                Unloadable,
                FileNotFound,
                BadImage,
                ReferencesAssemblies,
                InterfaceNotImplemented,
                BadRendererType,
                NoRendererConstructor,
            }

            private static Dictionary<LoadError, string> Messages;
            static Exception()
            {
                Messages = new Dictionary<LoadError, string>();
                Messages.Add(LoadError.StaticFields, "There must not be any classes defined in the assembly that have static fields as they can be used for inter-organism communication");
                Messages.Add(LoadError.NoCreatures, string.Format("There were no classes defined marked with the {0} attribute", typeof(OrganismAttribute).Name));
                Messages.Add(LoadError.NoConstructor, string.Format("This Organism does not have a constructor with an {0} parameter", typeof(OrganismStats).Name));
                Messages.Add(LoadError.NoStatsGenerator, string.Format("This Organism does not have a method matching the signature: public static {0} {1}()", typeof(OrganismStats).Name, GetOriginalStatsMethodName));
                Messages.Add(LoadError.StaticConstructor, "There must not be any classes defined in the assembly that have static constructors as they can call code outside of the time slice allocated for the organism and hang the application");
                Messages.Add(LoadError.FileNotFound, "Could not find the file containing the assembly, check that the file still exists");
                Messages.Add(LoadError.Unloadable, "Could not load the file containing the assembly, check that you have access to this file");
                Messages.Add(LoadError.BadImage, "The file selected is not a valid .Net v2 or v3 assembly");
                Messages.Add(LoadError.ReferencesAssemblies, "The assembly must not reference any non-standard libraries");
                Messages.Add(LoadError.InterfaceNotImplemented, string.Format("This public class is marked with the {0} attribute but does not implement {1}", typeof(OrganismAttribute).Name, typeof(IOrganism).Name));
                Messages.Add(LoadError.BadRendererType, string.Format("This Organism does not provide a renderer or the renderer does not implement {0};", typeof(IRenderer).Name));
                Messages.Add(LoadError.NoRendererConstructor, string.Format("The renderer for this Organism does not have a constructor with an {0} parameter", typeof(OrganismStats).Name));
            }

            private SortedList<LoadError, bool> errors = new SortedList<LoadError, bool>();

            public Exception()
            {
            }

            public Exception(LoadError error)
            {
                AddError(error);
            }

            public Exception(List<LoadError> errors)
            {
                foreach (LoadError error in errors)
                    AddError(error);
            }

            public void AddError(LoadError error)
            {
                if (!errors.ContainsKey(error))
                    errors.Add(error, true);
            }

            public bool HasErrors { get { return errors.Count != 0; } }

            public override string Message
            {
                get
                {
                    if (!HasErrors)
                        return string.Empty;
                    if (errors.Count == 1)
                        return Messages[errors.Keys[0]];
                    string[] messages = new string[errors.Count];
                    int message = 0;
                    foreach (LoadError error in errors.Keys)
                        if (Messages.ContainsKey(error))
                            messages[message++] = Messages[error];
                        else
                            messages[message++] = "#unknown error#";
                    return "There were errors in the provided assembly:\n"
                        + string.Join("\n", messages);
                }
            }
        }
    }
}
